#include <ostream>
#include "http_connection.h"
#include "qtch/log.h"
#include "http_parser.h"



namespace qtch {
namespace http {

Logger::ptr logger = QTCH_LOG_NAME("system");

HttpResult::HttpResult(Error result, HttpResponse::ptr response,const std::string& error) 
    :m_result(result)
    ,m_resonse(response)
    ,m_error(error){
}

std::string HttpResult::toString() const {
    std::stringstream ss;
    ss << "[HttpResult result = " << (int)m_result
       << " error=" << m_error
       << " response=" << (m_resonse ? m_resonse->toString() : "nullptr")
       << "]";
    return ss.str();
}


HttpConnection::HttpConnection(Socket::ptr sock, bool owner)
    :SocketStream(sock, owner)
    ,m_ba(new ByteArray){
    
}

HttpConnection::~HttpConnection() {
    QTCH_LOG_DEBUG(logger) << "HttpConnection::~HttpConnection";
}

HttpResponse::ptr HttpConnection::recvResponse() {
    m_ba->clearUnused();
    HttpResponseParser::ptr parser(new HttpResponseParser);
    uint64_t buff_size = HttpResponseParser::GetHttpResponseBufferSize();
    std::shared_ptr<char> buffer(
        new char[buff_size], [](char * ptr){
            delete[] ptr;
        });
    char * data = buffer.get();
    int offset = 0;
    do {
        int len = 0;
        if(m_ba->getReadSize()>0){
            len = (buff_size - offset > m_ba->getReadSize() ? m_ba->getReadSize() : buff_size - offset);
            m_ba->read(data + offset, len);
        } else {
            len = read(data + offset, buff_size - offset);
        }
        if(len <= 0){
            close();
            return nullptr;
        }
        len += offset;
        size_t nparse = parser->execute(data,len,false);
        if(parser->hasError()){
            close();
            return nullptr;
        }
        offset = len - nparse;
        if( offset == (int)buff_size){
            close();
            return nullptr;
        }
        if(parser->isFinished()){
            break;
        }
    }while(true);
    auto& client_parse = parser->getParser();
    std::string body;
    if(client_parse.chunked){
        int len = offset;
        do{
            bool begin = true;
            do{
                if(!begin || len == 0){
                    int rt = read(data + len, buff_size - len);
                    if(rt <= 0) {
                        close();
                        return nullptr;
                    }
                    len += rt;
                }
                data[len] = '\0';
                size_t nparse = parser->execute(data, len, true);
                if(parser->hasError()) {
                    close();
                    return nullptr;
                }
                len -= nparse;
                if(len == (int)buff_size) {
                    close();
                    return nullptr;
                }
                begin = false;
            }while(!parser->isFinished());

            if(client_parse.content_len + 2 <= len) {
                body.append(data, client_parse.content_len);
                memmove(data, data + client_parse.content_len + 2
                        , len - client_parse.content_len - 2);
                len -= client_parse.content_len + 2;
            } else {
                body.append(data, len);
                int left = client_parse.content_len - len + 2;
                while(left > 0) {
                    int rt = read(data, left > (int)buff_size ? (int)buff_size : left);
                    if(rt <= 0) {
                        close();
                        return nullptr;
                    }
                    body.append(data, rt);
                    left -= rt;
                }
                body.resize(body.size() - 2);
                len = 0;
            }

        }while(!client_parse.chunks_done);
    }else{
        int length = parser->getContentLength();
        if(length > 0){
            body.resize(length);
            int len = 0;
            if(length >= offset){
                memcpy(&body[0],data,offset);
                len = offset;
            } else {
                memcpy(&body[0],data,length);
                len = length;
                m_ba->write(data+length,offset - length);
            }
            length -= offset;
            if(length > 0){
                if(readFixSize(&body[len],length) <= 0){
                    close();
                    return nullptr;
                }
            }
        }
    }
    if(!body.empty()){
        auto content_encoding = parser->getData()->getHeader("content-encoding");
        if(!content_encoding.empty()){
            QTCH_LOG_ERROR(logger) << "need coding with content_encoding=" << content_encoding;
        }


        parser->getData()->setBody(body);
    }


    

    return parser->getData();
}

int HttpConnection::sendRequest(HttpRequest::ptr req) {
    std::stringstream ss;
    ss << req;
    std::string data = ss.str();
    return writeFixSize(data.c_str(),data.length());
}

HttpResult::ptr HttpConnection::DoGet(const std::string& url, uint64_t timeout_ms
                                , const std::map<std::string, std::string>& header
                                , const std::string& body){
    return DoRequest(HttpMethod::GET,url,timeout_ms,header,body);
}

HttpResult::ptr HttpConnection::DoPost(const std::string& url, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    return DoRequest(HttpMethod::POST,url,timeout_ms,header,body);
}

HttpResult::ptr HttpConnection::DoRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    Uri::ptr uri = Uri::Create(url);
    if(!uri){
        return std::make_shared<HttpResult>(HttpResult::Error::INVALID_URL,nullptr
            ,"invalid url:"+url);
    }
    return DoRequest(method,uri,timeout_ms,header,body);
}

HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req, const std::string& url, uint64_t timeout_ms){
    Uri::ptr uri = Uri::Create(url);
    if(!uri){
        return std::make_shared<HttpResult>(HttpResult::Error::INVALID_URL,nullptr
            ,"invalid url:"+url);
    }
    return DoRequest(req,uri,timeout_ms);
}

HttpResult::ptr HttpConnection::DoGet(Uri::ptr uri, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    return DoRequest(HttpMethod::GET,uri,timeout_ms,header,body);
}

HttpResult::ptr HttpConnection::DoPost(Uri::ptr uri, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    return DoRequest(HttpMethod::POST,uri,timeout_ms,header,body);
}

HttpResult::ptr HttpConnection::DoRequest(HttpMethod method, Uri::ptr uri, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    HttpRequest::ptr req = std::make_shared<HttpRequest>();
    req->setQuery(uri->getQuery());
    req->setPath(uri->getPath());
    req->setMethod(method);
    req->setBody(body);
    req->setFragment(uri->getFragment());
    for(auto& i: header){
        req->setHeader(i.first,i.second);
    }
    std::string connection = req->getHeaderAs<std::string>("connection");
    if(strcasecmp(connection.c_str(),"keep-alive")==0){
        req->setClose(false);
    }
    std::string host;
    if(!req->checkGetHeaderAs<std::string>("host",host)){
        req->setHeader("host",uri->getHost());
    }
    return DoRequest(req,uri,timeout_ms);
}

HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req, Uri::ptr uri, uint64_t timeout_ms){
    bool is_ssl = uri->getScheme() == "https";
    IPAddress::ptr addr = std::static_pointer_cast<IPAddress>(uri->createAddress());
    if(!addr){
        return std::make_shared<HttpResult>(HttpResult::Error::INVALID_HOST
                ,nullptr,"invalid host:"+ uri->getHost());
    }
    addr->setPort(uri->getPort());
    Socket::ptr sock = is_ssl ? SSLSocket::CreateTCP(addr) : Socket::CreateTCP(addr);
    if(!sock){
        return std::make_shared<HttpResult>(HttpResult::Error::CREAT_SOCKET_ERROR
                ,nullptr,"create socket fail::"+ addr->toString()
                    + " errno=" + std::to_string(errno)
                    + " errstr=" + strerror(errno));
    }
    if(!sock->connect(addr)){
        return std::make_shared<HttpResult>(HttpResult::Error::CONNECT_FAIL
                ,nullptr,"connect faile:"+ addr->toString());
    }
    sock->setRecvTimeout(timeout_ms);
    HttpConnection::ptr conn = std::make_shared<HttpConnection>(sock);
    int rt = conn->sendRequest(req);
    if(rt==0){
        return std::make_shared<HttpResult>(HttpResult::Error::SEND_CLOSE_PEER
                ,nullptr,"send close by peer:"+ addr->toString());
    }
    if(rt<0){
        return std::make_shared<HttpResult>(HttpResult::Error::SEND_SOCKET_ERROR
                ,nullptr,"send request socket error errno=:"+ std::to_string(errno)
                    + " strerr=" + strerror(errno));
    }
    auto rsp = conn->recvResponse();
    if(!rsp){
        return std::make_shared<HttpResult>(HttpResult::Error::TIMEOUT
                ,nullptr,"recv response timeout: "+ addr->toString()
                + " TIMEOUT=" + std::to_string(timeout_ms));
    }
    return std::make_shared<HttpResult>(HttpResult::Error::OK, rsp, "ok");

}

HttpConnectionPool::ptr HttpConnectionPool::Create(const std::string& uri
                                        , const std::string& v_host
                                        , uint32_t max_size
                                        , uint32_t max_alive_time
                                        , uint32_t max_request){
    Uri::ptr uri_ptr = Uri::Create(uri);
    if(!uri_ptr){
        QTCH_LOG_ERROR(logger) << "invalid uri:" << uri;
        return nullptr;
    }
    return std::make_shared<HttpConnectionPool>(uri_ptr->getHost(),v_host,uri_ptr->getPort()
                , uri_ptr->getScheme() == "https",max_size,max_alive_time,max_request);
}

HttpConnectionPool::HttpConnectionPool(const std::string& host
                    , const std::string& vhost
                    , uint32_t port
                    , bool isHttps
                    , uint32_t max_size
                    , uint32_t max_alive_time
                    , uint32_t max_request){
    m_host = host;
    m_vhost = vhost;
    m_port = port ? port : (isHttps? 443 : 80);
    m_isHttps = isHttps;
    m_maxSize = max_size;
    m_maxAliveTime = max_alive_time;
    m_maxRequest = max_request;

}
    
HttpConnection::ptr HttpConnectionPool::getConnection(){
    uint64_t now_ms = GetCurrentMS();
    std::vector<HttpConnection*> invalid_conn;
    HttpConnection* conn_ptr = nullptr;
    MutexType::Lock lock(m_mutex);
    while(!m_conns.empty()){
        auto conn = *m_conns.begin();
        m_conns.pop_front();
        if(!conn->isConnected()){
            invalid_conn.push_back(conn);
            continue;
        }
        if(conn->m_createTime + m_maxAliveTime < now_ms){
            invalid_conn.push_back(conn);
            continue;
        }
        conn_ptr = conn;
        break;
    }
    lock.unlock();
    for(auto i : invalid_conn){
        delete i;
    }
    m_total -= invalid_conn.size();

    if(!conn_ptr){
        IPAddress::ptr addr = Address::LookupAnyIPAdress(m_host);
        if(!addr){
            QTCH_LOG_ERROR(logger) << "invalid host:" << m_host;
            return nullptr;
        }
        addr->setPort(m_port);
        Socket::ptr sock = m_isHttps ? SSLSocket::CreateTCP(addr) : Socket::CreateTCP(addr);
        if(!sock){
            QTCH_LOG_ERROR(logger) << "create socket fail:" << addr->toString()
                        << " errno=" << std::to_string(errno)
                        << " errstr=" << strerror((errno));
            return nullptr;
        }
        if(!sock->connect(addr)){
            QTCH_LOG_ERROR(logger) << "connect faile:" << addr->toString();
            return nullptr;
        }
        conn_ptr = new HttpConnection(sock);
        ++m_total;
    }

    return HttpConnection::ptr(conn_ptr,std::bind(&HttpConnectionPool::ReleasePtr,std::placeholders::_1,this));

}

void HttpConnectionPool::ReleasePtr(HttpConnection* conn_ptr, HttpConnectionPool* pool){
    conn_ptr->m_request++;
    if(!conn_ptr->isConnected()
        || (conn_ptr->m_request >= pool->m_maxRequest)
        || (conn_ptr->m_createTime + pool->m_maxAliveTime <= GetCurrentMS())){
        delete conn_ptr;
        --pool->m_total;
        return;
    }
    MutexType::Lock lock(pool->m_mutex);
    pool->m_conns.push_back(conn_ptr);
}

HttpResult::ptr HttpConnectionPool::DoGet(const std::string& url, uint64_t timeout_ms
                    , const std::map<std::string, std::string>& header
                    , const std::string& body){
    return DoRequest(HttpMethod::GET,url,timeout_ms,header,body);
}

HttpResult::ptr HttpConnectionPool::DoPost(const std::string& url, uint64_t timeout_ms
                    , const std::map<std::string, std::string>& header
                    , const std::string& body){
    return DoRequest(HttpMethod::POST,url,timeout_ms,header,body);
}

HttpResult::ptr HttpConnectionPool::DoRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms
                        , const std::map<std::string, std::string>& header
                        , const std::string& body){
    Uri::ptr uri = Uri::Create(url);
    if(!uri){
        return std::make_shared<HttpResult>(HttpResult::Error::INVALID_URL,nullptr
            ,"invalid url:"+url);
    }
    return DoRequest(method,uri,timeout_ms,header,body);
}

HttpResult::ptr HttpConnectionPool::DoRequest(HttpRequest::ptr req, const std::string& url, uint64_t timeout_ms){
    Uri::ptr uri = Uri::Create(url);
    if(!uri){
        return std::make_shared<HttpResult>(HttpResult::Error::INVALID_URL,nullptr
            ,"invalid url:"+url);
    }
    return DoRequest(req,uri,timeout_ms);
}

HttpResult::ptr HttpConnectionPool::DoGet(Uri::ptr uri, uint64_t timeout_ms
                    , const std::map<std::string, std::string>& header
                    , const std::string& body){
    return DoRequest(HttpMethod::GET,uri,timeout_ms,header,body);
}

HttpResult::ptr HttpConnectionPool::DoPost(Uri::ptr uri, uint64_t timeout_ms
                    , const std::map<std::string, std::string>& header
                    , const std::string& body){
    return DoRequest(HttpMethod::POST,uri,timeout_ms,header,body);
}

HttpResult::ptr HttpConnectionPool::DoRequest(HttpMethod method, Uri::ptr uri, uint64_t timeout_ms
                    , const std::map<std::string, std::string>& header
                    , const std::string& body){
    HttpRequest::ptr req = std::make_shared<HttpRequest>();
    req->setQuery(uri->getQuery());
    req->setPath(uri->getPath());
    req->setMethod(method);
    req->setBody(body);
    req->setFragment(uri->getFragment());
    for(auto& i: header){
        req->setHeader(i.first,i.second);
    }
    std::string connection = req->getHeaderAs<std::string>("connection");
    if(strcasecmp(connection.c_str(),"keep-alive")==0){
        req->setClose(false);
    }
    std::string host;
    if(!req->checkGetHeaderAs<std::string>("host",host)){
        if(m_vhost.empty()){
            host = m_host;
        }else{
            host = m_vhost;
        }
        req->setHeader("host",host);
    }
    return DoRequest(req,uri,timeout_ms);
}

HttpResult::ptr HttpConnectionPool::DoRequest(HttpRequest::ptr req, Uri::ptr uri, uint64_t timeout_ms){
    req->setQuery(uri->getQuery());
    req->setPath(uri->getPath());
    req->setFragment(uri->getFragment());
    QTCH_LOG_DEBUG(logger) << "uri: query=" << uri->getQuery() << " path=" << uri->getPath() << " fragment=" << uri->getFragment();
    QTCH_LOG_DEBUG(logger) << "DoRequest:\n" << req;
    qtch::http::HttpConnection::ptr conn = getConnection();
    if(!conn){
        return std::make_shared<HttpResult>(HttpResult::Error::POOL_GET_CONNECTION
                ,nullptr,"pool host=" + m_host + " port=" + std::to_string(m_port));
    }
    auto sock = conn->getSocket();
    if(!sock){
        return std::make_shared<HttpResult>(HttpResult::Error::POOL_INVALID_CONNECTION
                ,nullptr,"pool host=" + m_host + " port=" + std::to_string(m_port));
    }
    sock->setRecvTimeout(timeout_ms);
    int rt = conn->sendRequest(req);
    if(rt==0){
        return std::make_shared<HttpResult>(HttpResult::Error::SEND_CLOSE_PEER
                ,nullptr,"send close by peer:"+ m_host);
    }
    if(rt<0){
        return std::make_shared<HttpResult>(HttpResult::Error::SEND_SOCKET_ERROR
                ,nullptr,"send request socket error errno=:"+ std::to_string(errno)
                    + " strerr=" + strerror(errno));
    }
    auto rsp = conn->recvResponse();
    if(!rsp){
        return std::make_shared<HttpResult>(HttpResult::Error::TIMEOUT
                ,nullptr,"recv response timeout: "+ m_host
                + " TIMEOUT=" + std::to_string(timeout_ms));
    }
    return std::make_shared<HttpResult>(HttpResult::Error::OK, rsp, "ok");
}


}
}